#include <stdlib.h>

#include "PackageHeades.h"

void debugMsg(const char* msg);
void debugMsg(const char* format, const char* msg);
void init_device();
void setFilter(char* filter);
void startSniffing();
void parsePackage(u_char *foo, const struct pcap_pkthdr *header, const u_char *data);
void printEthernet(const struct sniff_ethernet* eth, int caplen);
void printIp(const struct sniff_ip *ip);
void printTCP(const struct sniff_tcp *tcp);
void printUDP(const struct sniff_udp *udp);
void printPayload(const u_char *data, int pay_size);

void printBeacon(const sniff_80211_beacon* beacon);
void printWireless(const sniff_80211* wireless);

void print_hex_ascii_line(const u_char *payload, int len, int offset);
void print_payload(const u_char *payload, int len);

char    g_error_buffer[PCAP_ERRBUF_SIZE];
char*   g_device = NULL;
pcap_t* g_descriptor = NULL;

struct in_addr g_inet_address;
struct bpf_program g_filter;
bpf_u_int32 g_mask;
bpf_u_int32 g_ip;

int paramd = 0;
int g_count_pkg = 0;

bool isWireless = true;

int main(int argc, char** argv) {
    char str[100]="";
    char filter_app[100]="";

    /* Constructing an expression for the filter */
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "-d"))
            paramd = 1;
        else {
        strcat(str, argv[i]);
        strcat(str, " ");
        }
    }

    strcpy(filter_app, str);

    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "-d"))
            paramd = 1;
        else {
            strcat(str, argv[i]);
            strcat(str, " ");
        }
    }

    try {
        init_device();
        setFilter(filter_app);
        startSniffing();

    } catch (const char* error) {
        fprintf(stderr, "Error: %s\n", error);
    }

    pcap_close(g_descriptor);
        
    return (EXIT_SUCCESS);
}


void debugMsg(const char* msg) {
     fprintf(stdout, "Debug: %s\n", msg);
 }

void debugMsg(const char* format, const char* msg) {
    char buffer[35];
    sprintf(buffer, format, msg);
    debugMsg(buffer);

}

void init_device() {
    //get device name
    g_device = pcap_lookupdev(g_error_buffer);
    g_device = "eth1";
    if( g_device == NULL ) {
        throw g_error_buffer;
    }

    debugMsg("device for sniffing: %s", g_device);
    debugMsg("get network param");

/*    if( pcap_lookupnet(g_device, &g_ip, &g_mask, g_error_buffer) == -1 ){
        throw g_error_buffer;
    }

    g_inet_address.s_addr = g_ip;
    debugMsg("ip: %s", inet_ntoa(g_inet_address));

    g_inet_address.s_addr = g_mask;
    debugMsg("mask: %s", inet_ntoa(g_inet_address));
*/
    

    //opening device
    g_descriptor = pcap_open_live(g_device, MAX_BUFFER_SIZE, 1, 0, g_error_buffer);
    if( g_descriptor == NULL ) {
        throw g_error_buffer;
    }

    if( pcap_datalink( g_descriptor  ) == DLT_EN10MB  ) {
        debugMsg("Ethernet");
        isWireless = false;
    } else if( pcap_datalink( g_descriptor  ) == DLT_IEEE802_11  ) {
        debugMsg("IEEE 802.11");
    } else if( pcap_datalink( g_descriptor  ) == DLT_IEEE802_11_RADIO  ) {
        debugMsg("IEEE 802.11 RADIO");
    } else if( pcap_datalink( g_descriptor  ) == DLT_IEEE802_11_RADIO_AVS  ) {
        debugMsg("IEEE 802.11 RADIO AVS");
    } else {
        printf("Wrong datalink %d\n", pcap_datalink(g_descriptor) );
        throw pcap_geterr(g_descriptor); 
    }
/*
    debugMsg("Try set datalink IEEE802_11");

    if( pcap_set_datalink( g_descriptor, DLT_IEEE802_11  ) == 1 ) {
        throw "Cannot set datalink";
    }

    debugMsg("New datalink type is %s", pcap_datalink_val_to_name( pcap_datalink(g_descriptor) ));
*/
}

void setFilter(char* filter) {
    debugMsg("filter is %s", filter);

    if(pcap_compile(g_descriptor, &g_filter, filter, 0, g_mask) == -1) {
        throw pcap_geterr(g_descriptor);
    }

    debugMsg("filter compile success");

    if(pcap_setfilter(g_descriptor, &g_filter) == -1)  {
        throw pcap_geterr(g_descriptor);
    }
}

void startSniffing() {
    char* pError;

    if ( pcap_loop(g_descriptor, -1, parsePackage, NULL) == -1 ) {
        pError = pcap_geterr(g_descriptor);
        pcap_close(g_descriptor);
        throw pError;
    }

}


void printBeacon(const sniff_80211_beacon* beacon){

    printf("Beacon interval: %d  >>>  ", EXTRACT_LE_16BITS(&beacon->beacon_interval));
    
	const u_char *ch = beacon->ap;
	
	printf("Access point:  ");	
	
	for(int i = 0; *ch != 0x01 && *ch != 0x00; i++) {
		if (isprint(*ch))
			printf("%c", *ch);
		else
			printf(".");
		ch++;
	}
	printf("\n");
 
}


void printWireless(const sniff_80211* wireless) {

   
}


void parsePackage(u_char *foo, const struct pcap_pkthdr *header, const u_char *data) {
    const struct sniff_ethernet *ethernet;
    const struct sniff_ip *ip;
    const struct sniff_tcp *tcp;
    const struct sniff_udp *udp;
    const u_char* payload;

    int size_payload = 0;

    

	if(isWireless) {
		//  printf("Bytes read %d\n", header->caplen);
		printf("====================BEGIN=PACKAGE========================\n");
		printf("Packet #%d (%d bytes read)\n", ++g_count_pkg, header->caplen);

		printf("-------------------WIRELESS_HEADER-----------------------\n");
		const struct sniff_radio_type *radio = (const struct sniff_radio_type*)(data);
		int radio_len = EXTRACT_LE_16BITS(&radio->it_len);

		const struct sniff_80211 *wireless = (const struct sniff_80211*)(data + radio_len);

		printf("MAC dst: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X   >>>>   ",
		wireless->dst[0],wireless->dst[1], wireless->dst[2],
		wireless->dst[3],wireless->dst[4], wireless->dst[5]);

		printf("MAC src: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X   >>>>   ",
		wireless->src[0],wireless->src[1], wireless->src[2],
		wireless->src[3],wireless->src[4], wireless->src[5]);

		printf("BSSID: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X\n",
		wireless->bssid[0],wireless->bssid[1], wireless->bssid[2],
		wireless->bssid[3],wireless->bssid[4], wireless->bssid[5]);
		
        int header_len = radio_len + sizeof(const struct sniff_80211);
		if( (wireless->control & 0xFF) == 0x80  ) {		  
          printBeacon( (const struct sniff_80211_beacon*)(data + header_len ) );
		  return;
		  
		} else if ((wireless->control & 0xFF) == 0x08) {
		  ip = (struct sniff_ip*)(data + header_len + 8);
		  printIp(ip);
		} else {
		   return;
		}
	      
	     const u_char *wifi_payload = (const u_char*)(data + radio_len);

	     printPayload(wifi_payload, header->caplen);
/*	  
			ip = (struct sniff_ip*)(data + SIZE_ETHERNET);
			
			printIp(ip);

			switch(ip->ip_p) {
			case IPPROTO_TCP:
					tcp = (struct sniff_tcp*)(data + SIZE_ETHERNET + IP_HL(ip)*4);
					printTCP(tcp);

					size_payload = ntohs(ip->ip_len) - (IP_HL(ip)*4 + TH_OFF(tcp)*4);
					payload = (u_char *)(data + SIZE_ETHERNET + IP_HL(ip)*4 + TH_OFF(tcp)*4);

					printPayload(payload, size_payload);
					return;
			case IPPROTO_UDP:
					udp = (struct sniff_udp *)(data + SIZE_ETHERNET + IP_HL(ip)*4);

					printUDP(udp);

					size_payload = ntohs(ip->ip_len) - (IP_HL(ip)*4 + ntohs(udp->len));
					payload = (u_char *)(data + SIZE_ETHERNET + IP_HL(ip)*4 + ntohs(udp->len));

					printPayload(payload, size_payload);

					return;
			default:
					printf("   Protocol: unknown\n");
					return;
			}
      }
*/
	  
	  
	  
	  /*
      printf("RADIO TYPE: >>> Version: %d  >>>   Length: %d\n", radio->it_version, radio->it_len);

      const struct sniff_tmp *head = (const struct sniff_tmp*)(data + sizeof(const struct sniff_radio_type));
      
      for(int i = 0; i < 30; i++) {
          printf("%.2X ", head->header[i]);
      }
      printf("\n\n");
*/
      

      return;
   }

    ethernet = (struct sniff_ethernet*)(data);

    printEthernet(ethernet, header->caplen);

    if( ntohs(ethernet->ether_type) == ETH_PROTO_IP ) {
        ip = (struct sniff_ip*)(data + SIZE_ETHERNET);
        
        printIp(ip);

        switch(ip->ip_p) {
        case IPPROTO_TCP:
                tcp = (struct sniff_tcp*)(data + SIZE_ETHERNET + IP_HL(ip)*4);
                printTCP(tcp);

                size_payload = ntohs(ip->ip_len) - (IP_HL(ip)*4 + TH_OFF(tcp)*4);
                payload = (u_char *)(data + SIZE_ETHERNET + IP_HL(ip)*4 + TH_OFF(tcp)*4);

                printPayload(payload, size_payload);
                return;
        case IPPROTO_UDP:
                udp = (struct sniff_udp *)(data + SIZE_ETHERNET + IP_HL(ip)*4);

                printUDP(udp);

                size_payload = ntohs(ip->ip_len) - (IP_HL(ip)*4 + ntohs(udp->len));
                payload = (u_char *)(data + SIZE_ETHERNET + IP_HL(ip)*4 + ntohs(udp->len));

                printPayload(payload, size_payload);

                return;
        default:
                printf("   Protocol: unknown\n");
                return;
        }
        
    }

}

void printEthernet(const struct sniff_ethernet* eth, int caplen) {


    printf("====================BEGIN=PACKAGE========================\n");
    printf("Packet #%d (%d bytes read)\n", ++g_count_pkg, caplen);

    printf("-------------------ETHERNET_HEADER-----------------------\n");
    printf("MAC src:  :%.2X:%.2X:%.2X:%.2X:%.2X:%.2X  ====>  ",
                eth->ether_shost[0], eth->ether_shost[1], eth->ether_shost[2],
	        eth->ether_shost[3], eth->ether_shost[4], eth->ether_shost[5]);

    printf("MAC dst:  :%.2X:%.2X:%.2X:%.2X:%.2X:%.2X\n",
		eth->ether_dhost[0],eth->ether_dhost[1], eth->ether_dhost[2],
		eth->ether_dhost[3],eth->ether_dhost[4], eth->ether_dhost[5]);
    printf("Packet type ID field  :%#x\n", ntohs(eth->ether_type));
}

void printIp(const struct sniff_ip *ip) {

    printf("-----------------------IP_HEADER-------------------------\n");
    printf("IP version: %d  ", IP_V(ip));
    printf("IP header length: %d  ", IP_HL(ip));
    printf("TOS: %d  ", ip->ip_tos);
    printf("Total length :%d\n", ntohs(ip->ip_len));

    printf("ID :%d >>> ", ntohs(ip->ip_id));
    printf("Fragment offset :%#x >>> ", ntohs(ip->ip_off));
    printf("MF :%d >>> ", ntohs(ip->ip_off)&IP_MF?1:0);
    printf("DF :%d\n", ntohs(ip->ip_off)&IP_DF?1:0);

    printf("TTL :%d >>> ", ip->ip_ttl);
    printf("Protocol :%d >>> \n", ip->ip_p);

    //g_inet_address.s_addr = ip->ip_src;
    printf("IP src :%s   ====>   ", inet_ntoa(ip->ip_src));

    //g_inet_address.s_addr = ip->ip_dst;
    printf("IP dst :%s\n", inet_ntoa(ip->ip_dst));
}

void printTCP(const struct sniff_tcp *tcp) {
    printf("----------------------TCP_HEADER-------------------------\n");
    printf("Port src: %d ===> ", ntohs(tcp->th_sport));
    printf("Port dst: %d\n", ntohs(tcp->th_dport));
    printf("Seq: %d >>> ", ntohs(tcp->th_seq));
    printf("Ack num: %d >>> ", ntohs(tcp->th_ack));
    printf("Data offset: %d\n", TH_OFF(tcp));
    printf("FIN:%d,", (tcp->th_flags)&0x01);
    printf("SYN:%d,", ((tcp->th_flags) >> 1)&0x01);
    printf("RST:%d,", ((tcp->th_flags) >> 2)&0x01);
    printf("PSH:%d,", ((tcp->th_flags) >> 3)&0x01);
    printf("ACK:%d,", ((tcp->th_flags) >> 4)&0x01);
    printf("URG:%d,", ((tcp->th_flags) >> 5)&0x01);
    printf("ECE:%d,", ((tcp->th_flags) >> 6)&0x01);
    printf("CWR:%d\n",((tcp->th_flags) >> 7)&0x01);
    printf("Window :%d  >>>>>>>>>>>> ", ntohs(tcp->th_win));
    printf("Urgent pointer: %d\n", tcp->th_urp);
}

void printUDP(const struct sniff_udp *udp) {
    printf("----------------------UDP_HEADER-------------------------\n");
    printf("Port source        :%d\n", ntohs(udp->source));
    printf("Port destination   :%d\n", ntohs(udp->dest));
    printf("Length             :%d\n", ntohs(udp->len));
}

void printPayload(const u_char *data, int pay_size) {
    
    if (pay_size > 0) {
            printf("   Payload (%d bytes):\n", pay_size);
            print_payload(data, pay_size);
    }

//    if(port == 21 && (strcmp(data, "USER") || strcmp(data, "USER")) ) {
//        printf("FTP data: %s", data);
//    }
}


void print_payload(const u_char *payload, int len) {

	int len_rem = len;
	int line_width = 16;			/* number of bytes per line */
	int line_len;
	int offset = 0;					/* zero-based offset counter */
	const u_char *ch = payload;

	if (len <= 0)
		return;

	/* data fits on one line */
	if (len <= line_width) {
		print_hex_ascii_line(ch, len, offset);
		return;
	}

	/* data spans multiple lines */
	for ( ;; ) {
		/* compute current line length */
		line_len = line_width % len_rem;
		/* print line */
		print_hex_ascii_line(ch, line_len, offset);
		/* compute total remaining */
		len_rem = len_rem - line_len;
		/* shift pointer to remaining bytes to print */
		ch = ch + line_len;
		/* add offset */
		offset = offset + line_width;
		/* check if we have line width chars or less */
		if (len_rem <= line_width) {
			/* print last line and get out */
			print_hex_ascii_line(ch, len_rem, offset);
			break;
		}
	}

return;
}

void
print_hex_ascii_line(const u_char *payload, int len, int offset)
{

	int i;
	int gap;
	const u_char *ch;

	/* offset */
	printf("%05d   ", offset);

	/* hex */
	ch = payload;
	for(i = 0; i < len; i++) {
		printf("%02x ", *ch);
		ch++;
		/* print extra space after 8th byte for visual aid */
		if (i == 7)
			printf(" ");
	}
	/* print space to handle line less than 8 bytes */
	if (len < 8)
		printf(" ");

	/* fill hex gap with spaces if not full line */
	if (len < 16) {
		gap = 16 - len;
		for (i = 0; i < gap; i++) {
			printf("   ");
		}
	}
	printf("   ");

	/* ascii (if printable) */
	ch = payload;
	for(i = 0; i < len; i++) {
		if (isprint(*ch))
			printf("%c", *ch);
		else
			printf(".");
		ch++;
	}

	printf("\n");

return;
}
